﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using Protocolo;
using System.Threading;
using System.Security.Cryptography;
using System.Configuration;

namespace SistemaDeContenido
{
    class Client
    {
        public static TcpClient Connect(string ip, int port)
        {

            TcpClient client = null;
            try
            {
                // Create a TcpClient.
                client = new TcpClient(ip, port);
            }
            catch (ArgumentNullException e)
            {
                Console.WriteLine("ArgumentNullException: {0}", e);
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }

            return client;
        }

        public static UdpClient UdpConnect(int port) 
        {
            UdpClient client = null;
            try
            {
                // Create a UdpClient.
                client = new UdpClient(port);
            }
            catch (ArgumentNullException e)
            {
                Console.WriteLine("ArgumentNullException: {0}", e);
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }

            return client;
        }

        public static void SendFile(Menu ventana, string path)
        {
            //Discover Server
           int UDPPort = Int32.Parse(ConfigurationManager.AppSettings.GetValues("LocalUdpPort")[0]);
           UdpClient clientUdp = UdpConnect(UDPPort);
            IPEndPoint ServerEndPoint = DescubrirServidor(clientUdp);
            //Connect to server
            TcpClient client = Connect(ServerEndPoint.Address.ToString(), ServerEndPoint.Port);

            try
            {
                long tamañoArchivo = 0;
                string md5 = null;
                using (FileStream fs = File.OpenRead(path))
                {
                    tamañoArchivo = fs.Length;
                    using (MD5CryptoServiceProvider m = new MD5CryptoServiceProvider())
                    {
                        var md5a = m.ComputeHash(fs);
                        //StringBuilder sb = new StringBuilder();
                        //for (int i = 0; i < md5a.Length; i++)
                        //{
                        //    sb.Append(md5a[i].ToString("x2"));
                        //}
                        //md5 = sb.ToString();
                        md5 = BitConverter.ToString(md5a);
                    }
                }


                double dCantPaquetes = Math.Ceiling((double)tamañoArchivo / 256);
                int cantidadPaquetes = Convert.ToInt32(dCantPaquetes);


                int contadorPaquetes = 0;
                Byte[] dataBuffer = new Byte[5120];

                bool primeraVez = true;

                FileStream fas = File.OpenRead(path);
                // Get a client stream for reading and writing.
                NetworkStream stream = client.GetStream();
                string FileName = string.Empty;

                //Initialize progress bar
                ventana.GetProgessBar().Visible = true;
                ventana.GetProgessBar().Minimum = 1;
                ventana.GetProgessBar().Maximum = cantidadPaquetes;
                ventana.GetProgessBar().Value = 1;
                ventana.GetProgessBar().Step = 1;
                ventana.SetMessage("Enviando: " + FileName);

                while (contadorPaquetes < cantidadPaquetes)
                {
                    fas.Read(dataBuffer, 0, dataBuffer.Length);

                    // Translate the passed file into ASCII and store it as a Byte array.
                    Byte[] data = dataBuffer;

                    //Creates protocol packages
                    int Index = path.LastIndexOf("\\");
                    FileName = path.Substring(Index + 1);

                    var PaquetesEnviar = ProtocolREQUpload.CrearPaquetes(data, FileName, primeraVez, cantidadPaquetes, md5, tamañoArchivo);
                    primeraVez = false;

                    foreach (var item in PaquetesEnviar)
                    {
                        if (contadorPaquetes > cantidadPaquetes) break;
                        // Send the message to the connected TcpServer. 
                        stream.Write(item.ByteArray, 0, item.ByteArray.Length);

                        // Receive the TcpServer.response.
                        // Buffer to store the response bytes.
                        byte[] data2 = new Byte[256];

                        // String to store the response ASCII representation.
                        String responseData = String.Empty;

                        // Read the first batch of the TcpServer response bytes.
                        Int32 bytes = stream.Read(data2, 0, data2.Length);
                        responseData = System.Text.Encoding.ASCII.GetString(data2, 0, bytes);

                        ventana.GetProgessBar().PerformStep();
                        //ventana.SetMessage(String.Format("Received: {0}", responseData));
                        contadorPaquetes++;
                    }
                }
                //Show success message
                ventana.GetProgessBar().Refresh();
                ventana.GetProgessBar().Visible = false;
                ventana.SetMessage(String.Format("Enviado: {0}", FileName));

                // Close everything.
                stream.Close();
                client.Close();



            }
            catch (ArgumentNullException e)
            {
                Console.WriteLine("ArgumentNullException: {0}", e);               
                client.Close();
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);               
                client.Close();
                SendFile(ventana, path);
            }
            catch (Exception e) 
            {
                Console.WriteLine("SocketException: {0}", e);                
                client.Close();
                SendFile(ventana, path);
            }

        }


        public static void DownloadFile(Menu ventana, string NombreArchivo, int paquete, bool resume)
        {
            //Discover Server

            int UDPPort = Int32.Parse(ConfigurationManager.AppSettings.GetValues("LocalUdpPort")[0]);
             UdpClient clientUdp = UdpConnect(UDPPort);
            IPEndPoint ServerEndPoint = DescubrirServidor(clientUdp);
            //Connect to server
            TcpClient client = Connect(ServerEndPoint.Address.ToString(), ServerEndPoint.Port);
            string Folder = ConfigurationManager.AppSettings.GetValues("Folder")[0];
            int PaqueteABajar = paquete;
            FileStream fs = null;
            try
            {
                //Get a client stream for reading and writing.
                NetworkStream stream = client.GetStream();

                //Creates package request download file
                Paquete Paquete = ProtocolREQDownload.CrearPaquetes(NombreArchivo, PaqueteABajar);

                //Send Request
                stream.Write(Paquete.ByteArray, 0, Paquete.ByteArray.Length);

                //Byte array to read
                int CantBytes = 266;
                byte[] data = new byte[CantBytes];

                //Read file data, first package
                int BytesRead;
                BytesRead = stream.Read(data, 0, CantBytes);

                PaqueteREQ received = new PaqueteREQ(data);
                //string commando = string.Empty;
                //received.ByteArray = data;

                //Process data
                //Get MD5
                string MD5 = received.getMD5();

                double tamañoArchivoT = received.getTamañoArchivo();

                //Get Cantidad de paquetes
                int PaquetesTotal = received.GetCantidadPaquetes();

                //Initialize progress bar
                ventana.GetProgessBar().Visible = true;
                ventana.GetProgessBar().Minimum = 1;
                ventana.GetProgessBar().Maximum = PaquetesTotal;
                ventana.GetProgessBar().Value = 1;
                ventana.GetProgessBar().Step = 1;
                ventana.SetMessage("Descargando: " + NombreArchivo);

                int PaquetesRecibidos = 0;


                if (resume)
                {
                    fs = File.Open(Folder + NombreArchivo, FileMode.Append, FileAccess.Write, FileShare.None);
                    PaquetesRecibidos = PaqueteABajar;
                }
                else
                {
                    fs = File.Open(Folder + NombreArchivo, FileMode.Create, FileAccess.Write, FileShare.None);
                    PaquetesRecibidos = 1; //Primer paquete
                }

                byte[] msg;
                msg = Encoding.ASCII.GetBytes("Se recibio el archivo.");
                stream.Write(msg, 0, msg.Length);

                while (PaquetesRecibidos < PaquetesTotal && tamañoArchivoT > 0)
                {
                    while (!stream.DataAvailable && client.Connected) ;
                    if (!client.Connected)
                        throw new SocketException();

                    if (tamañoArchivoT < data.Length - 10)
                        data = new byte[Convert.ToInt32(tamañoArchivoT) + 10];

                    BytesRead = stream.Read(data, 0, data.Length);
                    received = new PaqueteREQUpload(data);
                    received.ByteArray = data;
                    //commando = received.GetCmd().ToString();

                    // Add information to the file.
                    if (data.Length < 266)
                    {
                        byte[] aux = (byte[])data.Skip(10).Take(data.Length - 10).ToArray();
                        fs.Write(aux, 0, aux.Length);
                    }
                    else
                        fs.Write(received.GetDatos(), 0, received.GetDatos().Length);

                    //fs.Write(received.GetDatos(), 0, received.GetDatos().Length);
                    PaquetesRecibidos++;
                    //Last received package
                    PaqueteABajar = PaquetesRecibidos;
                    msg = Encoding.ASCII.GetBytes("Se recibio el archivo.");
                    stream.Write(msg, 0, msg.Length);
                    ventana.GetProgessBar().PerformStep();
                    tamañoArchivoT -= received.GetDatos().Length;
                }

                //Show success message
                ventana.GetProgessBar().Refresh();
                ventana.GetProgessBar().Visible = false;
                ventana.SetMessage(String.Format("Descargado: {0}", NombreArchivo));

                //Flush filestream and close file.
                fs.Flush();
                fs.Close();

                string md5 = null;

                using (FileStream fss = File.OpenRead(Folder + NombreArchivo))
                {
                    using (MD5CryptoServiceProvider m = new MD5CryptoServiceProvider())
                    {
                        var md5a = m.ComputeHash(fss);
                        //StringBuilder sb = new StringBuilder();
                        //for (int i = 0; i < md5a.Length; i++)
                        //{
                        //    sb.Append(md5a[i].ToString("x2"));
                        //}
                        //md5 = sb.ToString();
                        md5 = BitConverter.ToString(md5a);
                    }

                }

                if (md5 == MD5)
                {
                    Console.WriteLine("Recibido");
                    msg = System.Text.Encoding.ASCII.GetBytes("Se recibio el archivo.");
                    stream.Write(msg, 0, msg.Length);
                }
                else 
                {
                    Console.WriteLine("No recibido");
                    msg = System.Text.Encoding.ASCII.GetBytes("Se recibio el archivo con inconsistencias.");
                    stream.Write(msg, 0, msg.Length);
                    File.Delete(Folder + NombreArchivo); 
                }
            }
            catch (SocketException)
            {
                if (fs != null)
                {
                    fs.Flush();
                    fs.Close();
                }
                //Continue receiving package
                DownloadFile(ventana, NombreArchivo, PaqueteABajar, true);
            }
            catch (IOException)
            {
                if (fs != null)
                {
                    fs.Flush();
                    fs.Close();
                }
                //Continue receiving package
                DownloadFile(ventana, NombreArchivo, PaqueteABajar, true);
            }
            catch (Exception)
            {
                if (fs != null)
                {
                    fs.Flush();
                    fs.Close();
                }
                //Continue receiving package
                DownloadFile(ventana, NombreArchivo, PaqueteABajar, true);
            }
        }

        public static IPEndPoint DescubrirServidor(UdpClient client)
        {
            IPAddress RemoteIP;
            int RemotePort;
            string UDPPort = ConfigurationManager.AppSettings.GetValues("LocalUdpPort")[0];
            var paqueteEnviar = ProtocolREQUDP.CrearPaquetes(Dns.GetHostName(), UDPPort);
            using (var udpClient = new UdpClient(AddressFamily.InterNetwork))
            {
                var address = IPAddress.Parse("224.100.0.1");
                int MUDPPort = Int32.Parse(ConfigurationManager.AppSettings.GetValues("MulitCastUdpPort")[0]);
                
                IPEndPoint ipEndPoint = new IPEndPoint(address, MUDPPort);
                udpClient.Send(paqueteEnviar.ByteArray, paqueteEnviar.ByteArray.Length, ipEndPoint);

                byte[] received = client.Receive(ref ipEndPoint);
                PaqueteRES recibido = new PaqueteRES(received);
                string DatosAux = Encoding.ASCII.GetString(recibido.GetDatos());
                RemoteIP = IPAddress.Parse(DatosAux.Split('@')[0]);
                RemotePort = Int32.Parse(DatosAux.Split('@')[1]);
                udpClient.Close();
                client.Close();
            }
            IPEndPoint ipEndPointReturn = new IPEndPoint(RemoteIP, RemotePort);

            return ipEndPointReturn;
        }
    }
}
